#pragma once

#include <sstream>
#include <fstream>
#include <list>
#include <memory>
#include "resource_xml.h"

#ifdef _MSC_VER
#define CYB_DEBUG_BREAK() __debugbreak()
#else
#define CYB_DEBUG_BREAK()()
#endif

#define CYB_LOGMESSAGE( level, ... ) cyb::g_log->Write( level, __FILE__, __LINE__, __VA_ARGS__ )
#define CYB_DEBUG( ... ) CYB_LOGMESSAGE( cyb::LogLevel::Debug, __VA_ARGS__ )
#define CYB_CHECK( _condition, ...) \
	do { \
		if ( !( _condition ) ) \
		{ \
			CYB_LOGMESSAGE( cyb::LogLevel::Debug, __VA_ARGS__ ); \
			CYB_DEBUG_BREAK(); \
		} \
	} while( 0 )
#define CYB_INFO( ... ) CYB_LOGMESSAGE( cyb::LogLevel::Info, __VA_ARGS__ )
#define CYB_WARNING( _condition, ...) \
	do { \
		if ( !( _condition ) ) \
		{ \
			CYB_LOGMESSAGE( cyb::LogLevel::Warning, __VA_ARGS__ ); \
		} \
	} while( 0 )
#define CYB_FATAL( _condition, ... ) \
	do { \
		if ( !( _condition ) ) \
		{ \
			CYB_LOGMESSAGE( cyb::LogLevel::Error, __VA_ARGS__ ); \
			abort(); \
		} \
	} while( 0 )

namespace cyb
{

enum class LogLevel
{
	Debug,
	Info,
	Warning,
	Error
};

class LogMessage;

class ILogger
{
public:
	virtual void Write( const LogMessage* message ) = 0;
};

class ILogPolicy
{
public:
	virtual bool ShouldLog( const LogMessage* message ) = 0;
	virtual std::string GetFormattedString( const LogMessage* message ) = 0;
	virtual void Write( const std::string& buffer ) = 0;
};

class LogMessage
{
public:
	LogMessage( ILogger* loggerCallback, LogLevel level, const std::string& sourceFile, uint32_t sourceLine );
	~LogMessage();

	template <class T> 
	void Write( const T& what )
	{
		m_stringStream << what;
	}

	std::string String() const;
	LogLevel Level() const;
	const std::string& SourceFile() const;
	uint32_t SourceLine() const;

private:
	ILogger* m_logCallback;
	LogLevel m_level;
	std::ostringstream m_stringStream;
	std::string m_sourceFile;
	uint32_t m_sourceLine;
};

class LogPolicySettings
{
public:
	LogPolicySettings( const std::shared_ptr<XmlElement> element );
	~LogPolicySettings() = default;

	bool ShouldLog( const LogMessage* message );
	std::string GetFormattedString( const LogMessage* message );

private:
	bool m_logDebug;
	bool m_logInfo;
	bool m_logWarning;
	bool m_logError;
	std::string m_format;
};

class CommonLogPolicy : public ILogPolicy
{
public:
	CommonLogPolicy( const std::shared_ptr<XmlElement> element );
	virtual bool ShouldLog( const LogMessage* message ) final;
	virtual std::string GetFormattedString( const LogMessage* message ) final;

private:
	LogPolicySettings m_settings;
};

class ConsoleLogPolicy : public CommonLogPolicy
{
public:
	ConsoleLogPolicy( const std::shared_ptr<XmlElement> element );
	virtual ~ConsoleLogPolicy() = default;
	virtual void Write( const std::string& buffer ) final;
};

class FileLogPolicy : public CommonLogPolicy
{
public:
	FileLogPolicy( const std::shared_ptr<XmlElement> element );
	virtual ~FileLogPolicy() = default;
	virtual void Write( const std::string& buffer ) final;

private:
	std::ofstream m_file;
};

class Logger : public ILogger
{
public:
	using PolicyList = std::list<std::unique_ptr<ILogPolicy>>;

	Logger() = default;
	~Logger() = default;

	void Reset( const std::shared_ptr<XmlElement> element );
	void AddPolicy( std::unique_ptr<ILogPolicy> logPolicy );
	void RemoveAllPolicies();

	template <typename... Args> 
	void Write( LogLevel level, const std::string& sourceFile, uint32_t sourceLine, const Args&... args )
	{
		LogMessage message( this, level, sourceFile, sourceLine );
		WriteMessageImpl( message, args... );
	}

private:
	virtual void Write( const LogMessage* message ) final;

	template <typename Last>
	void WriteMessageImpl( LogMessage& message, const Last& last )
	{
		message.Write( last );
	}

	template <typename First, typename... Rest>
	void WriteMessageImpl( LogMessage& message, const First& first, const Rest&... rest )
	{
		message.Write( first );
		WriteMessageImpl( message, rest... );
	}

	PolicyList m_policies;
};

extern Logger* g_log;

}	// namespace cyb